//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  EtwTraceConsumer.cs
//    
// Abstract:
//
//  This module implements the EtwTraceConsumer class.
//  
//--

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

namespace Microsoft.OfficeCommunicationsServer.Applications.Common.UnitTests
{
    /// <summary>
    /// Encapsulates the information associated with an ETW event.
    /// </summary>
    internal sealed class EventReadEventArgs : EventArgs
    {
        private Guid _eventGuid;
        private string _eventString;
        private byte _level;

        /// <summary>
        /// ctor.
        /// </summary>
        /// <param name="eventGuid">Guid associated with the event</param>
        /// <param name="eventString">String associated with the event</param>
        /// <param name="level">Level associated with the event</param>
        internal EventReadEventArgs(Guid eventGuid, string eventString, byte level)
        {
            _eventGuid = eventGuid;
            _eventString = eventString;
            _level = level;
        }

        /// <summary>
        /// Guid associated with the event.
        /// </summary>
        internal Guid EventGuid
        {
            get
            {
                return _eventGuid;
            }
        }

        /// <summary>
        /// String associated with the event.
        /// </summary>
        internal string EventString
        {
            get
            {
                return _eventString;
            }
        }

        /// <summary>
        /// Level associated with the event.
        /// </summary>
        internal byte Level
        {
            get
            {
                return _level;
            }
        }
    }

    /// <summary>
    /// Implementation of a real-time ETW tracing session for the benefit of the LoggerTest unit tests.
    /// </summary>
    internal sealed class EtwTraceConsumer
    {
        private ulong[] _handle = new ulong[1];
        private string _sessionName;
        private NativeMethods.EventTraceBufferCallback _eventTraceBufferCallback;
        private NativeMethods.EventCallback _eventCallback;

        /// <summary>
        /// ctor.
        /// </summary>
        /// <param name="name">Name of the ETW session</param>
        public EtwTraceConsumer(string sessionName)
        {
            Debug.Assert(!String.IsNullOrEmpty(sessionName), "!String.IsNullOrEmpty(sessionName)");
            
            _sessionName = sessionName;
            _eventTraceBufferCallback = new NativeMethods.EventTraceBufferCallback(OnBufferRead);
            _eventCallback = new NativeMethods.EventCallback(OnTraceEvent);
        }

        /// <summary>
        /// Opens an real-time ETW session for reading.
        /// </summary>
        public void OpenTrace()
        {
            Debug.Assert(_handle[0] == 0, "_handle == 0");

            // Initialize delegates that Etw uses as callbacks
            NativeMethods.EVENT_TRACE_LOGFILE logfile = new NativeMethods.EVENT_TRACE_LOGFILE();
            logfile.BufferCallback = _eventTraceBufferCallback;
            logfile.EventCallback = _eventCallback;
            logfile.LoggerName = _sessionName;
            logfile.LogfileName = null;
            logfile.LogFileMode = (uint)NativeMethods.EventTraceFileMode.RealTime;
            
            // Open sessions with Etw OpenTrace method
            _handle[0] = NativeMethods.OpenTrace(ref logfile);

            int lastError = Marshal.GetLastWin32Error();

            if (!NativeMethods.IsValidHandle(_handle[0]))
            {
                throw new Win32Exception(lastError);
            }
        }

        /// <summary>
        /// Releases unmanaged resources.
        /// </summary>
        public void CloseTrace()
        {
            if (_handle[0] != 0 && NativeMethods.IsValidHandle(_handle[0]))
            {
                uint processResult = NativeMethods.CloseTrace(_handle[0]);
                
                _handle[0] = 0;

                if (processResult != NativeMethods.ERROR_SUCCESS && processResult != NativeMethods.ERROR_CANCELLED)
                {
                    throw new Win32Exception((int)processResult);
                }
            }
        }

        /// <summary>
        /// Initiates reading of the ETW session events.
        /// </summary>
        public void ProcessTraceAsync()
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessTrace));
        }

        /// <summary>
        /// Starts reading the ETW session events.
        /// </summary>
        public unsafe void ProcessTrace(object state)
        {
            Debug.Assert(_handle[0] != 0 && NativeMethods.IsValidHandle(_handle[0]), "_handle[0] != 0 && NativeMethods.IsValidHandle(_handle[0])");

            long startFileTime = 0, stopFileTime = 0;

            NativeMethods.ProcessTrace(_handle, _handle.Length, ref startFileTime, ref stopFileTime);

            // Only call CloseTrace after ProcessTrace completes
            CloseTrace();
        }

        /// <summary>
        /// Raised when an ETW event is consumed.
        /// </summary>
        public event EventHandler<EventReadEventArgs> EventRead;

        /// <summary>
        /// De-serializes the user data associated with each ETW event in this session.
        /// </summary>
        /// <param name="eventTrace">ETW native representation of the event</param>
        /// <remarks>
        /// This method is called by ETW for each event in this session.
        /// </remarks>
        private unsafe void OnTraceEvent(ref NativeMethods.EVENT_TRACE eventTrace)
        {
            byte* pData = (byte*)eventTrace.MofData;
            int length = (int)eventTrace.MofLength;

            if (pData != null)
            {
                using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream(pData, length, length, FileAccess.Read))
                {
                    BinaryReader reader = new BinaryReader(stream, System.Text.Encoding.Unicode);
                    
                    // The first 16 bytes should be the GUID associated with this event
                    Guid guid = new Guid(reader.ReadBytes(16));

                    // The remaining bytes should be the message string associated with this event
                    char[] c = reader.ReadChars((int)(reader.BaseStream.Length - reader.BaseStream.Position - 1)/2);

                    RaiseEventRead(guid, new string(c), eventTrace.Level);

                    reader.Close();
                }
            }
        }

        /// <summary>
        /// Returns true to ensure that events continue to be processed.
        /// </summary>
        /// <param name="eventTraceLogfile"></param>
        /// <returns>
        /// true
        /// </returns>
        /// <remarks>
        /// This method is called by ETW for each buffer in this session.
        /// </remarks>
        private unsafe bool OnBufferRead(ref NativeMethods.EVENT_TRACE_LOGFILE eventTraceLogfile)
        {
            // Always return true to continue processing events
            return true;
        }

        /// <summary>
        /// Raises the EventRead event.
        /// </summary>
        /// <param name="eventGuid">Guid associated with the event</param>
        /// <param name="eventString">String associated with the event</param>
        /// <param name="level">Level associated with the event</param>
        private void RaiseEventRead(Guid eventGuid, string eventString, byte level)
        {
            EventHandler<EventReadEventArgs> eventRead = EventRead;

            if (eventRead != null)
            {
                eventRead(this, new EventReadEventArgs(eventGuid, eventString, level));
            }
        }
    }
}